﻿Imports System.Net
Imports System.ComponentModel
Imports System.Threading
Imports Microsoft.WindowsAPICodePack
Imports Microsoft.WindowsAPICodePack.Taskbar
Imports Microsoft.WindowsAPICodePack.Shell

'<ServiceModel.ServiceBehavior(InstanceContextMode:=ServiceModel.InstanceContextMode.Single)> _
<Plugin("4DA9015B-BA5A-4E40-A228-E24408DA2180")>
Public Class MainForm
    Implements IClient


    'Implements Contract.IApplicationService

    'Private WithEvents LiveCommentServer As LiveCommentServer
    Private IsClosing As Boolean
    Private Proxy As IOperationProxy
    Private Settings As Settings

    Private LiveProgramDictionary As New Dictionary(Of Integer, LiveProgram)
    Private LiveProgramsLockSlim As New System.Threading.ReaderWriterLockSlim

    Private StationIcons As New Dictionary(Of String, IconContext)
    Private StationIconsLockSlim As New System.Threading.ReaderWriterLockSlim

    Private QueuingIcons As New Queue(Of IconContext)
    Private QueuingIconsLockSlim As New System.Threading.ReaderWriterLockSlim
    Private QueuingIconsTimer As New Timer(AddressOf QueuingIconCallback, Nothing, -1, -1)

    Private OrderedLivePrograms As New List(Of LiveProgram)

    Private AlertWindowSemaphore As New System.Threading.Semaphore(10, 10)

    Private Headers As New Dictionary(Of HeaderKind, HeaderContext)
    Private SelectedLiveProgram As LiveProgram

    Private Const ApplicationId As String = "NamaAlertPortable"

    Private Enum HeaderKind
        None
        Alerted
        OnAir
        OffAir
    End Enum

    Private Class HeaderContext
        Private _Text As String
        Public Property Text() As String
            Get
                Return _Text
            End Get
            Set(ByVal value As String)
                _Text = value
            End Set
        End Property

        Private _RowCount As Integer
        Public Property RowCount() As Integer
            Get
                Return _RowCount
            End Get
            Set(ByVal value As Integer)
                _RowCount = value
            End Set
        End Property

        Private _IsExpanded As Boolean
        Public Property IsExpanded() As Boolean
            Get
                Return _IsExpanded
            End Get
            Set(ByVal value As Boolean)
                _IsExpanded = value
            End Set
        End Property
    End Class

#Region "CertificatePolicy Class"

    ' For Mono to access web site using ssl
    Private Class CertificatePolicy
        Implements ICertificatePolicy

        Public Function CheckValidationResult(ByVal srvPoint As System.Net.ServicePoint, ByVal certificate As System.Security.Cryptography.X509Certificates.X509Certificate, ByVal request As System.Net.WebRequest, ByVal certificateProblem As Integer) As Boolean Implements System.Net.ICertificatePolicy.CheckValidationResult
            Return True
        End Function
    End Class

#End Region

    Private Class IconContext
        Private _Id As String
        Public Property Id() As String
            Get
                Return _Id
            End Get
            Set(ByVal value As String)
                _Id = value
            End Set
        End Property

        Private _Uri As Uri
        Public Property Uri() As Uri
            Get
                Return _Uri
            End Get
            Set(ByVal value As Uri)
                _Uri = value
            End Set
        End Property

        Private _Icon As Bitmap
        Public Property Icon() As Bitmap
            Get
                Return _Icon
            End Get
            Set(ByVal value As Bitmap)
                _Icon = value
            End Set
        End Property

        Private _Thumbnail As Bitmap
        Public Property Thumbnail() As Bitmap
            Get
                Return _Thumbnail
            End Get
            Set(ByVal value As Bitmap)
                _Thumbnail = value
            End Set
        End Property

        Private _RowIndex As Integer
        Public Property RowIndex() As Integer
            Get
                Return _RowIndex
            End Get
            Set(ByVal value As Integer)
                _RowIndex = value
            End Set
        End Property

        Private _ColumnIndex As Integer
        Public Property ColumnIndex() As Integer
            Get
                Return _ColumnIndex
            End Get
            Set(ByVal value As Integer)
                _ColumnIndex = value
            End Set
        End Property

    End Class

#Region "Private properties"

    Private ReadOnly Property IsRunningOnMono() As Boolean
        Get
            Return Type.GetType("Mono.Runtime") IsNot Nothing
        End Get
    End Property

    Private _ApplicationPath As String
    Private ReadOnly Property ApplicationPath() As String
        Get
            If _ApplicationPath Is Nothing Then
                _ApplicationPath = System.Reflection.Assembly.GetEntryAssembly().Location
            End If
            Return _ApplicationPath
        End Get
    End Property

#End Region

#Region "Jump List"

    Private Sub ResetJumpList()

        TaskbarManager.Instance.ApplicationId = ApplicationId

        Dim list = JumpList.CreateJumpList
        list.ClearAllUserTasks()

        Dim tasks = New List(Of JumpListLink)

        tasks.Add(New JumpListLink(ApplicationPath, "設定") With {.Arguments = "/jumplist:settings"})
        tasks.Add(New JumpListLink(ApplicationPath, "マイページ") With {.Arguments = "/jumplist:mypage"})

        'tasks.Add(New JumpListLink("http://live.nicovideo.jp/my", "マイページ") With { _
        '    .IconReference = New IconReference("", 0), _
        '    .Arguments = ""})

        list.AddUserTasks(tasks.ToArray)
        list.Refresh()
    End Sub

#End Region



    Private Sub MainForm_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

    End Sub

    Private Sub MainForm_Shown(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Shown
        If IsRunningOnMono Then
            Exit Sub
        End If

        If Not TaskbarManager.IsPlatformSupported Then
            Exit Sub
        End If
        ResetJumpList()

    End Sub

    Private Sub MainForm_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
        IsClosing = True
        Me.Hide()
    End Sub

    Private Sub MainForm_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
        Proxy.CloseClient()
    End Sub

    Private Sub AddLiveProgramsDataGridViewRow(Optional ByVal count As Integer = 1)
        If count = 0 Then
            Exit Sub
        End If

        'Debug.Print("AddLiveProgramsDataGridViewRow Thread: " & System.Threading.Thread.CurrentThread.ManagedThreadId)
        CreateOrderedLiveProgramCollection()
        LiveProgramsDataGridView.Rows.Add(count)
        LiveProgramsDataGridView.Invalidate()

        'Threading.ThreadPool.QueueUserWorkItem(New Threading.WaitCallback(AddressOf ShowAlertWindow), program)
        ' http://icon.nimg.jp/community/s/co84791.jpg
        'AlertWindowSemaphore.WaitOne()
        'ShowAlertWindow(program)

    End Sub

    Private Sub RemoveLiveProgramsDataGridViewRow(Optional ByVal count As Integer = 1)
        If count = 0 Then
            Exit Sub
        End If

        ' TODO 高速化
        LiveProgramsDataGridView.SuspendLayout()
        For i = 1 To count
            LiveProgramsDataGridView.Rows.RemoveAt(LiveProgramsDataGridView.Rows.Count - 1)
        Next
        LiveProgramsDataGridView.ResumeLayout()

        CreateOrderedLiveProgramCollection()
        LiveProgramsDataGridView.Invalidate()
    End Sub



#Region "DataGridView"



    Private Sub LiveProgramsDataGridView_CellFormatting(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellFormattingEventArgs) Handles LiveProgramsDataGridView.CellFormatting

        If Not TypeOf LiveProgramsDataGridView.Columns(e.ColumnIndex) Is DataGridViewLinkColumn Then
            Exit Sub
        End If

        ' LinkColumn Color 設定
        Dim cell = DirectCast(LiveProgramsDataGridView.Rows(e.RowIndex).Cells(e.ColumnIndex), DataGridViewLinkCell)
        If LiveProgramsDataGridView.Rows(e.RowIndex).Selected Then
            cell.LinkColor = Color.White
        Else
            cell.LinkColor = DirectCast(LiveProgramsDataGridView.Columns(e.ColumnIndex), DataGridViewLinkColumn).LinkColor
        End If
    End Sub

    Private Sub LiveProgramsDataGridView_CellClick(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles LiveProgramsDataGridView.CellClick
        If e.RowIndex = -1 Then
            Exit Sub
        End If

        Dim program = GetSelectedLiveProgram(e.RowIndex)
        If program IsNot Nothing Then
            Exit Sub
        End If

        Dim context As HeaderContext = Nothing
        If Not IsHeaderRow(e.RowIndex, context) Then
            Exit Sub
        End If

        CreateOrderedLiveProgramCollection()

        context.IsExpanded = Not context.IsExpanded
        If context.IsExpanded Then
            AddLiveProgramsDataGridViewRow(context.RowCount)
        Else
            RemoveLiveProgramsDataGridViewRow(context.RowCount)
        End If


    End Sub

    Private Sub LiveProgramsDataGridView_CellContentClick(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles LiveProgramsDataGridView.CellContentClick
        Debug.Print("LiveProgramsDataGridView_CellContentClick Thread: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId)

        If e.RowIndex = -1 Then
            Exit Sub
        End If

        Dim program = GetSelectedLiveProgram(e.RowIndex)
        If program Is Nothing Then
            Exit Sub
        End If

        Select Case e.ColumnIndex
            Case 1
                If ViewerPath <> "" Then
                    Process.Start(ViewerPath, String.Format("lv{0} /singleinstance", program.Id))
                Else
                    Process.Start(program.Uri.ToString)
                End If

            Case 2

        End Select

    End Sub

    Private Sub LiveProgramsDataGridView_CellContentDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles LiveProgramsDataGridView.CellContentDoubleClick

    End Sub

    Private Function IsHeaderRow(ByVal rowIndex As Integer, Optional ByRef context As HeaderContext = Nothing) As Boolean

        Dim index As Integer
        Dim offset As Integer
        For Each h In Headers.Values
            offset += 1

            If rowIndex = offset - 1 Then
                context = h
                Return True
            End If

            If Not h.IsExpanded Then
                Continue For
            End If

            If offset <= rowIndex AndAlso rowIndex < offset + h.RowCount Then
                index += rowIndex - offset
                Exit For
            End If

            offset += h.RowCount
            index += h.RowCount
        Next

        Return False

    End Function

    Private Sub LiveProgramsDataGridView_RowPrePaint(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewRowPrePaintEventArgs) Handles LiveProgramsDataGridView.RowPrePaint

        If (e.PaintParts And DataGridViewPaintParts.Background) = 0 AndAlso _
        (e.PaintParts And DataGridViewPaintParts.ContentForeground) = 0 Then
            Exit Sub
        End If

        Dim context As HeaderContext = Nothing
        If Not IsHeaderRow(e.RowIndex, context) Then
            Exit Sub
        End If

        ' 背景
        Dim backColor As Color
        If (e.PaintParts And DataGridViewPaintParts.SelectionBackground) > 0 AndAlso _
        (e.State And DataGridViewElementStates.Selected) > 0 Then
            backColor = Color.FromArgb(235, 235, 235)

        Else
            backColor = Color.White
        End If

        If (e.PaintParts And DataGridViewPaintParts.Background) > 0 Then

            Dim width As Integer
            If IsRunningOnMono Then
                width = e.RowBounds.Width
            Else
                width = LiveProgramsDataGridView.Columns.GetColumnsWidth(DataGridViewElementStates.Visible)
            End If
            Using b = New SolidBrush(backColor)
                e.Graphics.FillRectangle(b, New RectangleF( _
                e.RowBounds.X, _
                e.RowBounds.Y, _
                width, _
                e.RowBounds.Height))
            End Using
        End If


        ' [+]画像, テキスト
        Dim image As Bitmap
        If context.IsExpanded Then
            image = My.Resources.ToggleImage
        Else
            image = My.Resources.ExpandToggleImage
        End If
        Const offsetY As Integer = 1
        If (e.PaintParts And DataGridViewPaintParts.ContentForeground) > 0 Then
            e.Graphics.DrawImage(image, _
            New RectangleF(e.RowBounds.X + 5, _
            e.RowBounds.Y + (e.RowBounds.Height - image.Height) \ 2 + offsetY, _
            image.Width, _
            image.Height))

            Using b = New SolidBrush(LiveProgramsDataGridView.ForeColor)
                Dim text = String.Format("{0}({1})", context.Text, context.RowCount)
                Dim size = e.Graphics.MeasureString(text, LiveProgramsDataGridView.RowHeadersDefaultCellStyle.Font, e.RowBounds.Size, StringFormat.GenericDefault)
                e.Graphics.DrawString(text, _
                LiveProgramsDataGridView.RowHeadersDefaultCellStyle.Font, _
                b, _
                e.RowBounds.X + 20, _
                e.RowBounds.Y + (e.RowBounds.Height - Convert.ToInt32(size.Height)) \ 2)
            End Using
        End If

        e.PaintParts = e.PaintParts And Not (
            DataGridViewPaintParts.Background Or
            DataGridViewPaintParts.ContentForeground Or
            DataGridViewPaintParts.SelectionBackground)


    End Sub

    Private Sub LiveProgramsDataGridView_RowPostPaint(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewRowPostPaintEventArgs) Handles LiveProgramsDataGridView.RowPostPaint

        Dim context As HeaderContext = Nothing

        If Not IsHeaderRow(e.RowIndex, context) Then
            Exit Sub
        End If

        Using pen = New Pen(Color.LightGray)
            e.Graphics.DrawLine(pen, e.RowBounds.X, e.RowBounds.Y + e.RowBounds.Height - 1, e.RowBounds.X + e.RowBounds.Width, e.RowBounds.Y + e.RowBounds.Height - 1)
        End Using


    End Sub

    Private Sub LiveProgramsDataGridView_ColumnWidthChanged(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewColumnEventArgs) Handles LiveProgramsDataGridView.ColumnWidthChanged
        ' ヘッダー用 再描画
        LiveProgramsDataGridView.Invalidate()
    End Sub

    Private Function GetSelectedLiveProgram(ByVal rowIndex As Integer) As LiveProgram

        Dim index As Integer
        Dim offset As Integer
        Dim rowCount = 0
        Dim headerCount = 0

        If IsHeaderRow(rowIndex) Then
            Return Nothing
        End If

        For Each h In Headers.Values
            headerCount += 1
            If Not h.IsExpanded Then
                offset += h.RowCount
            End If

            rowCount += h.RowCount

            If rowCount >= offset + rowIndex - headerCount Then
                index = offset + rowIndex - headerCount
                Exit For
            End If
        Next

        If index < 0 Then
            Return Nothing
        End If

        ' TODO Nothingの場合調査
        If OrderedLivePrograms.Count - 1 < index Then
            Return Nothing
        End If

        Return OrderedLivePrograms(index)
    End Function

    Private Sub LiveProgramsDataGridView_CellValueNeeded(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellValueEventArgs) Handles LiveProgramsDataGridView.CellValueNeeded
        'Debug.Print("LiveProgramsDataGridView_CellValueNeeded Thread: {0}, Row: {1}, Col: {2}", System.Threading.Thread.CurrentThread.ManagedThreadId, e.RowIndex, e.ColumnIndex)

        Dim program = GetSelectedLiveProgram(e.RowIndex)
        If program Is Nothing Then
            If e.ColumnIndex = 1 AndAlso IsRunningOnMono Then
                ' for MONO
                e.Value = "|"
            End If
            Exit Sub
        End If

        Select Case e.ColumnIndex
            Case 0
                ' アイコン
                Exit Select
                StationIconsLockSlim.EnterUpgradeableReadLock()
                If StationIcons.ContainsKey(program.Station.Id) Then
                    e.Value = StationIcons(program.Station.Id).Thumbnail
                Else
                    StationIconsLockSlim.EnterWriteLock()
                    StationIcons.Add(program.Station.Id, New IconContext)
                    StationIconsLockSlim.ExitWriteLock()

                    Dim iconContext = New IconContext With {
                        .Id = program.Station.Id, _
                        .Uri = program.Station.IconUri, _
                        .RowIndex = e.RowIndex, _
                        .ColumnIndex = e.ColumnIndex}
                    QueuingIconsLockSlim.EnterWriteLock()
                    QueuingIcons.Enqueue(iconContext)
                    QueuingIconsLockSlim.ExitWriteLock()
                    QueuingIconsTimer.Change(0, -1)
                End If
                StationIconsLockSlim.ExitUpgradeableReadLock()

            Case 1
                ' タイトル
                e.Value = program.Title

            Case 2
                ' 放送局
                e.Value = program.Station.Name

            Case 3
                ' 開始時間
                If program.IsOnAir Then
                    e.Value = program.StartDateTime.ToString("HH:mm")
                Else
                    e.Value = program.StartDateTime.ToString("MM/dd HH:mm")
                End If

            Case 4
                ' 放送者
                e.Value = program.CasterName
        End Select



    End Sub

    Private Sub LiveProgramsDataGridView_ColumnHeaderMouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellMouseEventArgs) Handles LiveProgramsDataGridView.ColumnHeaderMouseClick

    End Sub

#End Region

#Region "Icon"

    Private Sub QueuingIconCallback(ByVal state As Object)
        'Debug.Print("QueuingIconCallback Thread: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId)
        QueuingIconsTimer.Change(-1, -1)

        Dim context As IconContext = Nothing
        Try
            QueuingIconsLockSlim.EnterWriteLock()
            If QueuingIcons.Count = 0 Then
                Exit Sub
            End If
            context = QueuingIcons.Dequeue
        Catch ex As Exception
            ' Ignore
        Finally
            QueuingIconsLockSlim.ExitWriteLock()
        End Try

        Try
            DownloadIcon(context)
            ResizeIcon(context)

            StationIconsLockSlim.EnterWriteLock()
            If Not StationIcons.ContainsKey(context.Id) Then
                StationIcons.Add(context.Id, context)
            Else
                StationIcons(context.Id) = context
            End If
            StationIconsLockSlim.ExitWriteLock()

            Me.Invoke(New Action(Of IconContext)(AddressOf ShowStationThumbnailIcon), New Object() {context})

        Catch webEx As WebException
            Exit Sub
        Catch ex As Exception
            Exit Sub

        Finally
            QueuingIconsLockSlim.EnterReadLock()
            If QueuingIcons.Count > 0 Then
                QueuingIconsTimer.Change(0, -1)
            End If
            QueuingIconsLockSlim.ExitReadLock()
        End Try

    End Sub

    Private Sub DownloadIcon(ByVal context As IconContext)
        Dim bmp As Bitmap
        Dim client = New WebClient
        Using stream = client.OpenRead(context.Uri)
            bmp = New Bitmap(stream)
            context.Icon = bmp
        End Using
    End Sub

    Private Sub ResizeIcon(ByVal context As IconContext)
        Dim thumb = New Bitmap(16, 16)
        Using g = Graphics.FromImage(thumb)
            g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High
            g.DrawImage(context.Icon, 0, 0, 16, 16)
            g.Dispose()
        End Using
        context.Thumbnail = thumb
    End Sub

    Private Sub ShowStationThumbnailIcon(ByVal context As IconContext)
        'Debug.Print("ShowStationThumbnailIcon Thread: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId)
        'LiveProgramsDataGridView.UpdateCellValue(context.ColumnIndex, context.RowIndex)

        If LiveProgramsDataGridView.SelectedRows.Count = 0 Then
            Exit Sub
        End If

        Dim index = LiveProgramsDataGridView.SelectedRows(0).Index
        Dim program = GetSelectedLiveProgram(index)
        If program Is Nothing Then
            IconPictureBox.Image = Nothing
            Exit Sub
        End If



        StationIconsLockSlim.EnterReadLock()
        If StationIcons.ContainsKey(program.Station.Id) Then
            IconPictureBox.Image = StationIcons(program.Station.Id).Icon
        Else
            IconPictureBox.Image = Nothing
        End If
        StationIconsLockSlim.ExitReadLock()

    End Sub


    Private Sub ToolStripButton1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ToolStripButton1.Click

        Dim fd = New FontDialog
        fd.Font = LiveProgramsDataGridView.DefaultCellStyle.Font

        If fd.ShowDialog = Windows.Forms.DialogResult.OK Then
            LiveProgramsDataGridView.RowHeadersDefaultCellStyle.Font = fd.Font
            LiveProgramsDataGridView.DefaultCellStyle.Font = fd.Font
            LiveProgramsDataGridView.AutoResizeRows()
        End If
        fd.Dispose()
    End Sub

#End Region

    Private ViewerPath As String

#Region "IClient"

    Public Sub AddFavorite(ByVal id As String) Implements IOperationProxy.AddFavorite
        Dim ids = New List(Of Integer)

        LiveProgramsLockSlim.EnterReadLock()
        Dim ps = From p In LiveProgramDictionary.Values
                 Where p.Station.Id = id _
                 AndAlso Not p.Station.IsFavorite

        For Each p In ps
            p.Station.IsFavorite = True
            ids.Add(p.Id)
        Next
        LiveProgramsLockSlim.ExitReadLock()

        For Each i In ids
            Me.NotifyLiveProgramAlerted(i)
        Next

    End Sub

    Public Sub RemoveFavorite(ByVal id As String) Implements IOperationProxy.RemoveFavorite

    End Sub

    Public Sub Alert(ByVal pluginId As System.Guid, ByVal program As LiveProgram, ByVal state As Object) Implements IOperationProxy.Alert
        Throw New NotImplementedException
    End Sub

    Public Function GetFavoriteLivePrograms() As System.Collections.Generic.IList(Of LiveProgram) Implements IOperationProxy.GetFavoriteLivePrograms
        Throw New NotImplementedException
    End Function

    Public Function GetOnAirLivePrograms() As System.Collections.Generic.IList(Of LiveProgram) Implements IOperationProxy.GetOnAirLivePrograms
        Throw New NotImplementedException
    End Function

    Public Sub CloseClient() Implements IOperationProxy.CloseClient
        If Not IsClosing Then
            Me.Close()
        End If
    End Sub

    Public Sub HideClient() Implements IOperationProxy.HideClient
        Me.Hide()
    End Sub

    Public Sub ShowClient() Implements IOperationProxy.ShowClient
        Me.Show()
    End Sub

    Public Function GetSettings() As Settings Implements IPlugin.GetSettings
        Return Me.Settings
    End Function

    Public Sub Initialize(ByVal proxy As IOperationProxy, ByVal settings As Settings) Implements IPlugin.Initialize
        Me.Proxy = proxy
        Me.Settings = settings

        ' Network
        If IsRunningOnMono Then
            ' MEMO MONO doesn't implement ServerCertificateValidationCallback
            System.Net.ServicePointManager.CertificatePolicy = New CertificatePolicy
        End If

        '' NamaAlert API
        'If Not IsRunningOnMono Then
        ''ApplicationServer.Open(Me)
        'End If

        ' Header
        Headers.Add(HeaderKind.Alerted, New HeaderContext With {.Text = "アラート", .IsExpanded = True})
        Headers.Add(HeaderKind.OnAir, New HeaderContext With {.Text = "オンエア", .IsExpanded = True})
        Headers.Add(HeaderKind.OffAir, New HeaderContext With {.Text = "オフエア", .IsExpanded = True})

        LiveProgramsDataGridView.Rows.Add(Headers.Count)

        ' DataGridView
        Dim type = GetType(DataGridView)
        Dim propInfo = type.GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic)
        propInfo.SetValue(LiveProgramsDataGridView, True, Nothing)

    End Sub

    Protected Sub ShowDialog1(ByVal owner As System.Windows.Forms.IWin32Window) Implements IPlugin.ShowDialog

    End Sub

    Public Sub NotifyClientStatusChanged(ByVal status As Object) Implements IPlugin.NotifyClientStatusChanged

    End Sub

    Public Sub NotifyLiveProgramAlerted(ByVal id As Integer) Implements IWorkerPlugin.NotifyLiveProgramAlerted

        Dim needRemoving = False
        Dim needAdding = False

        Try
            LiveProgramsLockSlim.EnterReadLock()
            If Not LiveProgramDictionary.ContainsKey(id) Then
                Exit Sub
            End If

            If LiveProgramDictionary(id).IsAlerted Then
                Exit Sub
            End If

            LiveProgramDictionary(id).IsAlerted = True

            If LiveProgramDictionary(id).IsOnAir Then
                Interlocked.Decrement(Headers(HeaderKind.OnAir).RowCount)
                If Headers(HeaderKind.OnAir).IsExpanded Then
                    needRemoving = True
                    'Me.Invoke(New Action(AddressOf RemoveLiveProgramsDataGridViewRow))
                End If
            Else
                Interlocked.Decrement(Headers(HeaderKind.OffAir).RowCount)
                If Headers(HeaderKind.OffAir).IsExpanded Then
                    needRemoving = True
                    '
                End If
            End If
            Interlocked.Increment(Headers(HeaderKind.Alerted).RowCount)
            If Headers(HeaderKind.Alerted).IsExpanded Then
                needAdding = True
            End If

        Catch ex As Exception
        Finally
            LiveProgramsLockSlim.ExitReadLock()
        End Try

        If needRemoving Then
            Me.Invoke(New Action(AddressOf RemoveLiveProgramsDataGridViewRow))
        End If
        If needAdding Then
            Me.Invoke(New Action(AddressOf AddLiveProgramsDataGridViewRow))
        End If
        'CreateOrderedLiveProgramCollection()

    End Sub

    Public Sub NotifyLiveProgramClosed(ByVal id As Integer) Implements IWorkerPlugin.NotifyLiveProgramClosed
        If IsClosing Then
            Exit Sub
        End If

        Dim needRemoving = False
        Dim needAdding = False

        Try
            LiveProgramsLockSlim.EnterWriteLock()
            If Not LiveProgramDictionary.ContainsKey(id) Then
                Exit Sub
            End If

            Dim p = LiveProgramDictionary(id)

            If p.IsAlerted Then
                Interlocked.Decrement(Headers(HeaderKind.Alerted).RowCount)
                If Headers(HeaderKind.Alerted).IsExpanded Then
                    needRemoving = True
                End If
            Else
                Interlocked.Decrement(Headers(HeaderKind.OnAir).RowCount)
                If Headers(HeaderKind.OnAir).IsExpanded Then
                    needRemoving = True
                End If
            End If

            If p.Station.IsFavorite Then
                Interlocked.Increment(Headers(HeaderKind.OffAir).RowCount)
                If Headers(HeaderKind.OffAir).IsExpanded Then
                    needAdding = True
                End If
            End If

            LiveProgramDictionary.Remove(p.Id)

        Catch ex As Exception
            ' Ignore
        Finally
            LiveProgramsLockSlim.ExitWriteLock()
        End Try

        If needRemoving Then
            Me.Invoke(New Action(AddressOf RemoveLiveProgramsDataGridViewRow))
        End If
        If needAdding Then
            Me.Invoke(New Action(AddressOf AddLiveProgramsDataGridViewRow))
        End If

    End Sub

    Public Sub NotifyLiveProgramPreviewStart(ByVal notification As LiveProgramNotification) Implements IWorkerPlugin.NotifyLiveProgramPreviewStart

    End Sub

    Public Sub NotifyLiveProgramStartedOrUpdated(ByVal notification As LiveProgramNotification) Implements IWorkerPlugin.NotifyLiveProgramStartedOrUpdated
        If IsClosing Then
            Exit Sub
        End If
        Dim program = notification.LiveProgram
        'Debug.WriteLine("NotifyLiveProgramStartedOrUpdated " & program.Title & " thread = " & System.Threading.Thread.CurrentThread.ManagedThreadId.ToString)
        Try

            Dim p = DirectCast(program.Clone, LiveProgram)
            Try
                LiveProgramsLockSlim.EnterWriteLock()
                If LiveProgramDictionary.ContainsKey(p.Id) Then
                    ' TODO 表示更新
                    LiveProgramDictionary(p.Id) = p
                    Exit Sub
                Else
                    LiveProgramDictionary.Add(p.Id, p)
                End If
            Catch ex As Exception
                ' Ignore
            Finally
                LiveProgramsLockSlim.ExitWriteLock()
            End Try

            ' Add row
            If p.IsOnAir Then
                Interlocked.Increment(Headers(HeaderKind.OnAir).RowCount)
                If Headers(HeaderKind.OnAir).IsExpanded Then
                    Me.Invoke(New Action(AddressOf AddLiveProgramsDataGridViewRow))
                Else
                    Me.Invoke(New Action(AddressOf InvalidateLiveProgramsDataGridView))
                End If
            Else
                Interlocked.Increment(Headers(HeaderKind.OffAir).RowCount)
                If Headers(HeaderKind.OffAir).IsExpanded Then
                    Me.Invoke(New Action(AddressOf AddLiveProgramsDataGridViewRow))
                Else
                    Me.Invoke(New Action(AddressOf InvalidateLiveProgramsDataGridView))
                End If
            End If


        Catch ex As Exception
            ' Ignore
#If DEBUG Then
            Stop
#End If
        End Try


    End Sub

#End Region

    Private Sub CreateOrderedLiveProgramCollection()

        LiveProgramsLockSlim.EnterReadLock()
        Dim ps = From p In LiveProgramDictionary.Values
        Order By p.IsAlerted Descending, p.IsOnAir Descending, p.StartDateTime Descending

        OrderedLivePrograms.Clear()
        For Each p In ps
            OrderedLivePrograms.Add(p)
        Next

        'Dim program = DirectCast(ps(index).Clone, LiveProgram)
        LiveProgramsLockSlim.ExitReadLock()

    End Sub

    Private Sub InvalidateLiveProgramsDataGridView()
        LiveProgramsDataGridView.Invalidate()
    End Sub



    Private Sub LiveProgramsDataGridView_SelectionChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles LiveProgramsDataGridView.SelectionChanged

        If LiveProgramsDataGridView.SelectedRows.Count = 0 Then
            Exit Sub
        End If

        Dim index = LiveProgramsDataGridView.SelectedRows(0).Index

        SelectedLiveProgram = GetSelectedLiveProgram(index)
        If SelectedLiveProgram Is Nothing Then
            IconPictureBox.Image = Nothing
            Exit Sub
        End If


        StationIconsLockSlim.EnterUpgradeableReadLock()
        If StationIcons.ContainsKey(SelectedLiveProgram.Station.Id) Then
            IconPictureBox.Image = StationIcons(SelectedLiveProgram.Station.Id).Icon

        Else
            StationIconsLockSlim.EnterWriteLock()
            StationIcons.Add(SelectedLiveProgram.Station.Id, New IconContext)
            StationIconsLockSlim.ExitWriteLock()

            Dim iconContext = New IconContext With {
                .Id = SelectedLiveProgram.Station.Id, _
                .Uri = SelectedLiveProgram.Station.IconUri, _
                .RowIndex = 0, _
                .ColumnIndex = 0}
            QueuingIconsLockSlim.EnterWriteLock()
            QueuingIcons.Enqueue(iconContext)
            QueuingIconsLockSlim.ExitWriteLock()
            QueuingIconsTimer.Change(0, -1)
        End If
        StationIconsLockSlim.ExitUpgradeableReadLock()




    End Sub

    Private Sub FavoriteButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles FavoriteButton.Click
        If SelectedLiveProgram Is Nothing Then
            Exit Sub
        End If

        If SelectedLiveProgram.Station.IsFavorite Then
            Exit Sub
        End If

        Proxy.AddFavorite(SelectedLiveProgram.Station.Id)

    End Sub

    Public Sub SetAlertGroups(ByVal alertGroups As System.Collections.Generic.IList(Of AlertGroup)) Implements IClient.SetAlertGroups

    End Sub

    Public Sub ShowAlertGroupClient() Implements IOperationProxy.ShowAlertGroupClient

    End Sub

    Public Sub ChangeAlertGroup(ByVal stationId As String, ByVal alertGroupId As System.Guid) Implements IOperationProxy.ChangeAlertGroup

    End Sub
End Class
